/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/graphics.hpp>
#include <baja/graphics/scenes.hpp>
#include <journal/common/memory_tracking_interfaces.h>

namespace journal { namespace services { namespace memory_tracking {

namespace b = baja;
namespace g = baja::graphics;
namespace gs = baja::graphics::scenes;
namespace gd = baja::graphics::dx11_1::details;

class memory_tracking_service :
    public imemory_tracking_service
{
public:
    memory_tracking_service();
    static std::shared_ptr<memory_tracking_service> create(const std::shared_ptr<g::iengine>& graphics);

    void collect(const b::uint32 typeToken, const size_t sizeInBytes);
    void uncollect(const b::uint32 typeToken, const size_t sizeInBytes);
    void reset(const b::uint32 typeToken, bool restart = false);
    void resetAll(bool restart = false);
    void memory_tracking_service::get(
        const b::uint32 typeToken,
        b::float32* count,
        b::float32* value
        );

    // irender_observer
    void onRender();
    void onScreenChange(const baja::app::screen_change_args& args);
    void onResize(const a::resize_args& args);
    void onVisibilityChanged(const bool visible);

    // imemory_tracking_service
    void registerType(
        const baja::uint32& typeToken,
        const std::wstring& name,
        const b::Boolean& cumulative
        );

    baja::uint32 registerType(
        const std::wstring& name,
        const b::Boolean& cumulative
        );

    void logCreate(
        const baja::uint32& typeToken, 
        const baja::uint32& id,
        const size_t& sizeInBytes
        );

    void logExpand(
        const baja::uint32& typeToken, 
        const baja::uint32& id,
        const size_t& sizeInBytes
        );

    void logShrink(
        const baja::uint32& typeToken, 
        const baja::uint32& id,
        const size_t& sizeInBytes
        );

    void logDispose(
        const baja::uint32& typeToken, 
        const baja::uint32& id,
        const size_t& sizeInBytes
        );

private:
    std::shared_ptr<g::ifont> getFont();
    void init(const std::shared_ptr<g::iengine>& graphics);
    void calculatePositions(
        const int slot,
        const b::uint32 e,
        const b::wchar* topLabel, 
        const b::wchar* bottomLabel, 
        const b::float32 previousTop,
        m::rectangle* topLabelRect,
        m::rectangle* bottomLabelRect,
        m::rectangle* graphicRect,
        m::rectangle* totalRect
        );
    void renderSlot(
        const b::uint32 typeToken,
        const std::wstring& name,
        const size_t& count,
        const size_t& sizeInBytes,
        b::float32& previousTop);
    void renderGraphic(const b::uint32 slot, const b::uint32 typeToken, const m::rectangle* rect);
    m::point4d mapValue(double value, double max, int i, const m::rectangle* rect);

    struct data_point
    {
        size_t count;
        double value;
    };

    struct circular_buffer
    {
        circular_buffer(const int size) : _over(false), _size(size), _current(-1) { _points.resize(size); }

        void add() 
        { 
            _current++; 
            if (_current >= _size) { _over = true; _current %= _size; }
        }

        data_point* at(const int i) { return (_current >= 0) ? &_points[index(i)] : nullptr; }
        int length() { return _over ? _size : _current+1; }
        data_point* current() { return at(length() - 1); }
        data_point* previous() { return at(length() - 2); }
        int index(const int i)
        {
            int first = (_over ? _current+1 : 0);
            return (first + i) % _size;
        }

        void reset()
        {
            _over = false;
            _current = -1;
        }

        bool _over;
        int _size;
        int _current;
        std::vector<data_point> _points;
    };

    struct data
    {
        data();
        bool dirty;
        bool init;
        circular_buffer buffer;
        double lastResetMs; // timestamp of the last reset
        double lastNonZero;
        double lastNonZeroAvg;
        std::recursive_mutex _mutex;
        data& operator=(const data& other);

        void reset()
        {
            dirty = false;
            init = false;
            buffer.reset();
            lastNonZero = 0;
            lastNonZeroAvg = 0;

        }
    };

    struct type_info
    {
        std::wstring typeName;
        size_t instanceCount;
        size_t sizeInBytes;
        b::Boolean cumulative;
        std::shared_ptr<data> _data;

        type_info() : instanceCount(0), sizeInBytes(0) {}
    };

    std::map<b::uint32, type_info> _memoryMap;

    b::stopwatch _sw;
    std::shared_ptr<g::ifont> _font;
    m::rectangle _vp;
    double _lastResetMs;

    static const b::float32 _margin;
    static const b::float32 _graphicHeight;
    static const b::float32 _graphicWidth;
    static const b::float32 _labelHeight;
    static const b::uint32 _backdropAlpha;

    static const b::uint32 _baseInternalTypeToken;
    b::uint32 _internalTypeToken;

    bool _firstRender;

    std::recursive_mutex _mutex;
};

std::shared_ptr<imemory_tracking_service> createMemoryTrackingService(const std::shared_ptr<g::iengine>& graphics);

}}} // journal::services